package cn.ante.ymcc.service.impl;

import cn.ante.ymcc.domain.AlipayInfo;
import cn.ante.ymcc.domain.PayOrder;
import cn.ante.ymcc.dto.ApplyPayParamDto;
import cn.ante.ymcc.dto.Order2PayDto;
import cn.ante.ymcc.mapper.PayOrderMapper;
import cn.ante.ymcc.service.IAlipayInfoService;
import cn.ante.ymcc.service.IPayOrderService;
import cn.ante.ymcc.util.AssertUtil;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeCloseResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Nai
 * @since 2022-08-19
 */
@Service
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {

    @Autowired
    private IAlipayInfoService alipayInfoService;

    /**
     * @Description: 消费消息要保证幂等性，一个消息只能成功保存一个支付单，所以的先去支付单中查询
     * @Author: Nai
     * @Version: 1.0
     * @Date: 2022/8/20 22:35
     */
    @Override
    public void savePayOrder(Order2PayDto order2PayDto) {
        String orderNo = order2PayDto.getOrderNo();
        Wrapper<PayOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", orderNo);
        PayOrder payOrder = selectOne(wrapper);
        // 这里使用if而不是断言，断言会导致签收失败，下次继续消费
        if (payOrder == null) {
            payOrder = new PayOrder();
            BeanUtils.copyProperties(order2PayDto, payOrder);
            payOrder.setCreateTime(new Date());
            payOrder.setPayStatus(PayOrder.STATE_WILL_PAY);
            insert(payOrder);
        }

    }

    /**
     * @Description: 根据订单号查询支付单  不能断言抛错，不然不会轮巡，这里
     * @Author: Nai
     * @Version: 1.0
     * @Date: 2022/8/20 22:57
     */
    @Override
    public PayOrder checkPayOrder(String orderNo) {
        Wrapper<PayOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no", orderNo);
        return selectOne(wrapper);
    }

    /**
    * @Description: 订单超时未支付取消
    * @Author: Nai
    * @Version: 1.0
    * @Date:  2022/8/26 0:24
    */
    @Override
    public void cancelPayOrderByPayTimeOut(String orderNo) {
        PayOrder payOrder = checkPayOrder(orderNo);
        // 判断支付订单是否存在
        if (payOrder == null){
            return;
        }
        // 判断支付表单的状态是不是待支付
        if (payOrder.getPayStatus() != 0){
            return;
        }
        // 当支付状态是待支付时  就将其设置取消订单
        payOrder.setPayStatus(4); // 订单超时取消支付订单
        payOrder.setUpdateTime(new Date());
        updateById(payOrder);

        // 关闭支付宝交易
        try {
            AlipayTradeCloseResponse closeResponse = Factory.Payment.Common().close(orderNo);
            // 关闭成功  关闭失败就是只支付成功了，要走退款流程
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 触发支付  调用支付宝支付  响应支付宝调用结果(form表单
     * @Author: Nai
     * @Version: 1.0
     * @Date: 2022/8/20 23:11
     */
    @Override
    public String apply(ApplyPayParamDto applyPayParamDto) {
        // jsar303校验
        // 校验支付单是否存在
        String orderNo = applyPayParamDto.getOrderNo();
        PayOrder payOrder = checkPayOrder(orderNo);
        AssertUtil.isNotNull(payOrder, "支付单不存在");
        boolean b = payOrder.getPayStatus() == PayOrder.STATE_WILL_PAY;
        AssertUtil.isTrue(b, "非法请求");
        // 调支付宝进行支付  调用对应的支付方式  前端传过来有paytype
        int payType = payOrder.getPayType().intValue();
        if (payType == 1) { // 支付宝
            return aliPay(payOrder, applyPayParamDto);
        } else if (payType == 2) {
            return null;
        } else if (payType == 3) {
            return null;
        }
        return null;
    }


    /**
     * @Description: 调用ali支付
     * @Author: Nai
     * @Version: 1.0
     * @Date: 2022/8/20 23:47
     */
    public String aliPay(PayOrder payOrder, ApplyPayParamDto applyPayParamDto) {
        AlipayInfo alipayInfo = alipayInfoService.selectList(null).get(0);
        // 1. 设置参数（全局只需设置一次）
        Factory.setOptions(getOptions(alipayInfo));
        try {
            // 2. 发起API调用（以创建扫码收款二维码为例）
            AlipayTradePagePayResponse response = Factory.Payment.Page().pay(
                    payOrder.getSubject(),
                    payOrder.getOrderNo(),
                    payOrder.getAmount().toString(),
                    StringUtils.isEmpty(applyPayParamDto.getCallUrl()) ? alipayInfo.getReturnUrl() : applyPayParamDto.getCallUrl());
            // 3. 处理响应或异常
            if (ResponseChecker.success(response)) {
                System.out.println("调用成功");
                return response.getBody();
            } else {
                System.err.println("调用失败，原因：" + response);
            }
        } catch (Exception e) {
            System.err.println("调用遭遇异常，原因：" + e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * @Description: 动态设置阿狸支付配置
     * @Author: Nai
     * @Version: 1.0
     * @Date: 2022/8/20 23:47
     */
    private Config getOptions(AlipayInfo alipayInfo) {
        Config config = new Config();
        config.protocol = alipayInfo.getProtocol();
        config.gatewayHost = alipayInfo.getGatewayHost();
        config.signType = alipayInfo.getSignType();
        config.appId = alipayInfo.getAppId();
        // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
        config.merchantPrivateKey = alipayInfo.getMerchantPrivateKey();
        //注：证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径，优先从文件系统中加载，加载失败后会继续尝试从CLASS_PATH中加载
        //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
        config.alipayPublicKey = alipayInfo.getAlipayPublicKey();
        //可设置异步通知接收服务地址（可选）
        config.notifyUrl = alipayInfo.getNotifyUrl();
        //可设置AES密钥，调用AES加解密相关接口时需要（可选）
        return config;
    }
}
